﻿using System;
using EntityFramework.DbContextScope.Interfaces;
using Numero3.EntityFramework.Demo.CommandModel;
using Numero3.EntityFramework.Demo.DomainModel;
using Numero3.EntityFramework.Demo.Repositories;

namespace Numero3.EntityFramework.Demo.BusinessLogicServices
{
	/*
	 * Example business logic service implementing command functionalities (i.e. create / update actions).
	 */
	public class UserCreationService
	{
		private readonly IDbContextScopeFactory _dbContextScopeFactory;
		private readonly IUserRepository _userRepository;

		public UserCreationService(IDbContextScopeFactory dbContextScopeFactory, IUserRepository userRepository)
		{
			if (dbContextScopeFactory == null) throw new ArgumentNullException(nameof(dbContextScopeFactory));
			if (userRepository == null) throw new ArgumentNullException(nameof(userRepository));
			_dbContextScopeFactory = dbContextScopeFactory;
			_userRepository = userRepository;
		}

		public void CreateUser(UserCreationSpec userToCreate)
		{
			if (userToCreate == null)
				throw new ArgumentNullException(nameof(userToCreate));

			userToCreate.Validate();

			/*
			 * Typical usage of DbContextScope for a read-write business transaction. 
			 * It's as simple as it looks.
			 */
			using (IDbContextScope dbContextScope = _dbContextScopeFactory.Create())
			{
				//-- Build domain model
				User user = new User()
				           {
							   Id = userToCreate.Id,
							   Name = userToCreate.Name,
							   Email = userToCreate.Email,
							   WelcomeEmailSent = false,
					           CreatedOn = DateTime.UtcNow
				           };

				//-- Persist
				_userRepository.Add(user);
				dbContextScope.SaveChanges();
			}
		}

		public void CreateListOfUsers(params UserCreationSpec[] usersToCreate)
		{
			/*
			 * Example of DbContextScope nesting in action. 
			 * 
			 * We already have a service method - CreateUser() - that knows how to create a new user
			 * and implements all the business rules around the creation of a new user 
			 * (e.g. validation, initialization, sending notifications to other domain model objects...).
			 * 
			 * So we'll just call it in a loop to create the list of new users we've 
			 * been asked to create.
			 * 
			 * Of course, since this is a business logic service method, we are making 
			 * an implicit guarantee to whoever is calling us that the changes we make to 
			 * the system will be either committed or rolled-back in an atomic manner. 
			 * I.e. either all the users we've been asked to create will get persisted
			 * or none of them will. It would be disastrous to have a partial failure here
			 * and end up with some users but not all having been created.
			 * 
			 * DbContextScope makes this trivial to implement. 
			 * 
			 * The inner DbContextScope instance that the CreateUser() method creates
			 * will join our top-level scope. This ensures that the same DbContext instance is
			 * going to be used throughout this business transaction.
			 * 
			 */
			/*

			*DbContextScope嵌套操作的示例。
			*
			*我们已经有一个服务方法-create user（）-它知道如何创建新用户
			*并实现围绕创建新用户的所有业务规则
			*（例如验证、初始化、向其他域模型对象发送通知…）。
			*
			*所以我们将循环调用它来创建新用户列表
			*被要求创建。
			*
			*当然，由于这是一种业务逻辑服务方法，我们正在
			*对任何打电话给我们的人的一个隐含的保证
			*系统将以原子方式提交或回滚。
			*也就是说，我们被要求创建的所有用户都将被持久化
			*否则他们都不会。在这里发生局部故障将是灾难性的
			*最终会有一些用户，但并不是所有用户都被创建。
			*
			*DbContextScope使实现这一点变得很简单。
			*
			*CreateUser（）方法创建的内部DbContextScope实例
			*将加入我们的顶级范围。这确保了相同的DbContext实例
			*将在整个业务交易中使用。
			*
			*/
			using (IDbContextScope dbContextScope = _dbContextScopeFactory.Create())
			{
				foreach (UserCreationSpec toCreate in usersToCreate)
				{
					CreateUser(toCreate);
				}

				// All the changes will get persisted here
				dbContextScope.SaveChanges();
			}
		}

		public void CreateListOfUsersWithIntentionalFailure(params UserCreationSpec[] usersToCreate)
		{
			/*
			 * Here, we'll verify that inner DbContextScopes really join the parent scope and 
			 * don't persist their changes until the parent scope completes successfully. 
			 */

			bool firstUser = true;

			using (IDbContextScope dbContextScope = _dbContextScopeFactory.Create())
			{
				foreach (UserCreationSpec toCreate in usersToCreate)
				{
					if (firstUser)
					{
						CreateUser(toCreate);
						Console.WriteLine("Successfully created a new User named '{0}'.", toCreate.Name);
						firstUser = false;
					}
					else
					{
						// OK. So we've successfully persisted one user.
						// We're going to simulate a failure when attempting to 
						// persist the second user and see what ends up getting 
						// persisted in the DB.
						throw new Exception($"Oh no! An error occurred when attempting to create user named '{toCreate.Name}' in our database.");
					}
				}

				dbContextScope.SaveChanges();
			}
		}
	}
}

